package security.config.parser;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import security.core.filter.LoginFilter;
import security.core.filter.LogoutFilter;
import security.core.filter.PrivilegeFilter;
import security.core.filter.SessionContextFilter;
import security.core.filter.handler.impl.RBACLoginProcessHandler;
import security.core.filter.handler.impl.RBACLoginResultHandler;
import security.core.filter.handler.impl.RBACLogoutProcessHandler;
import security.core.filter.handler.impl.RBACLogoutSuccessHandler;
import security.core.filter.handler.impl.RBACPrivilegeProcessHandler;
import security.core.manager.AuthenticationManager;
import security.core.manager.PrivilegeManager;

public class RBACConfigBuilder {
	// bean id for filters
	public static final String LOGOUT_FILTER_BEAN_ID = "RBACLogoutFilter";
	public static final String LOGIN_FINLTER_BEAN_ID = "RBACLoginFilter";
	public static final String SESSION_CONTEXT_FILTER_BEAN_ID = "RBACSessionContextFilter";
	public static final String PRIVILEGE_FILTER_BEAN_ID = "RBACPrivilegeFilter";
	
	// bean id for default handler
	private final String DEFAULT_LOGOUT_SUCCESS_HANDLER_BEAN_ID = "RBACLogoutSuccessHandler";
	private final String DEFAULT_LOGOUT_PROCESS_HANDLER_BEAN_ID = "RBACLogoutProcessHandler";
	private final String DEFAULT_LOGIN_PROCESS_HANDLER_BEAN_ID = "RBACLoginProcessHandler";
	private final String DEFAULT_LOGIN_RESULT_HANDLER_BEAN_ID = "RBACLoginResultHandler";
	private final String DEFAULT_PRIVILEGE_PROCESS_HANDLER_BEAN_ID = "RBACPrivilegeProcessHandler";
	
	// bean id for default manager
	private final String DEFAULT_AUTHENTICATION_MANAGER_BEAN_ID = "RBACAuthenticationManager";
	private final String DEFAULT_PRIVILEGE_MANAGER_BEAN_ID = "RBACPrivilegeManager";
	
	private Element rootElement;
	private ParserContext parserContext;
	
	public RBACConfigBuilder(Element element, ParserContext pc) {
		this.rootElement = element;
		this.parserContext = pc;
	}
	
	public String parseLoginPage() {
		Element ignoreEle = DomUtils.getChildElementByTagName(rootElement, "ignore-list");
		return ignoreEle.getAttribute("login-page");
	}
	
	// parse the ignore-list element
	public List<String> parseIgnoreList() {
		List<String> ignore = new ArrayList<String>();
		Element ignoreEle = DomUtils.getChildElementByTagName(rootElement, "ignore-list");
		if (ignoreEle != null) {
			for (Element pattern: DomUtils.getChildElements(ignoreEle)) {
				ignore.add(pattern.getTextContent());
			}
		}
		ignore.add(parseLoginPage());
		return ignore;
	}
	
	// parse the session-context-filter element
	public void createSessionContextFilter() {
		// register the session context filter
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(SessionContextFilter.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyValue("loginPage", parseLoginPage());
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, SESSION_CONTEXT_FILTER_BEAN_ID));		
	}
	
	// parse the logout-filter element
	public void createLogoutFilter() {
		String successHandlerName;
		Element logoutEle = DomUtils.getChildElementByTagName(rootElement, "logout-filter");
		// check if there is custom logout success handler
		// otherwise use the default handler
		if (DomUtils.getChildElementByTagName(logoutEle, "logout-success-handler") != null) {
			successHandlerName = DomUtils.getChildElementByTagName(logoutEle, "logout-success-handler").getTextContent();
		} else {
			createDefaultLogoutSuccessHanler();
			successHandlerName = DEFAULT_LOGOUT_SUCCESS_HANDLER_BEAN_ID;
		}
		// no custom logout process handler is supported
		createDefaultLogoutProcessHanler();
		// set the process url and the redirect url
		String logoutProcessURL = logoutEle.getAttribute("process-url");
		String logoutRedirectURL = logoutEle.getAttribute("redirect-url");
		
		// register the logout filter bean
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyValue("logoutProcessURL", logoutProcessURL);
		beanBuilder.addPropertyValue("logoutRedirectURL", logoutRedirectURL);
		beanBuilder.addPropertyReference("logoutSuccessHandler", successHandlerName);
		beanBuilder.addPropertyReference("logoutProcessHandler", DEFAULT_LOGOUT_PROCESS_HANDLER_BEAN_ID);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, LOGOUT_FILTER_BEAN_ID));
	}
	
	// parse the login-filter
	public void createLoginFilter() {
		String resultHandlerName;
		Element loginEle = DomUtils.getChildElementByTagName(rootElement, "login-filter");
		if (DomUtils.getChildElementByTagName(loginEle, "login-result-handler") != null) {
			resultHandlerName = DomUtils.getChildElementByTagName(loginEle, "login-result-handler").getTextContent();
		} else {
			createDefaultLoginResultHandler();
			resultHandlerName = DEFAULT_LOGIN_RESULT_HANDLER_BEAN_ID;
		}
		String loginProcessURL = loginEle.getAttribute("process-url");
		String loginRedirectURL = loginEle.getAttribute("redirect-url");
		createDefaultLoginProcessHandler();
		createAuthenticationManager();
		// register the login filter bean
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(LoginFilter.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyValue("loginProcessURL", loginProcessURL);
		beanBuilder.addPropertyValue("loginRedirectURL", loginRedirectURL);
		beanBuilder.addPropertyValue("loginPage", parseLoginPage());
		beanBuilder.addPropertyReference("loginProcessHandler", DEFAULT_LOGIN_PROCESS_HANDLER_BEAN_ID);
		beanBuilder.addPropertyReference("loginResultHandler", resultHandlerName);
		beanBuilder.addPropertyReference("authenticationManager", DEFAULT_AUTHENTICATION_MANAGER_BEAN_ID);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, LOGIN_FINLTER_BEAN_ID));
	}
	
	// parse the privilege-filter
	public void createPrivilegeFilter() {
		createPrivilegeManager();
		createDefaultPrivilegeProcessHandler();
		// register the privilege filter
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(PrivilegeFilter.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyReference("privilegeManager", DEFAULT_PRIVILEGE_MANAGER_BEAN_ID);
		beanBuilder.addPropertyReference("privilegeProcessHandler", DEFAULT_PRIVILEGE_PROCESS_HANDLER_BEAN_ID);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, PRIVILEGE_FILTER_BEAN_ID));		
	}
	
	// create default handlers
	
	private void createDefaultLogoutSuccessHanler() {
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(RBACLogoutSuccessHandler.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_LOGOUT_SUCCESS_HANDLER_BEAN_ID));
	}
	
	private void createDefaultLogoutProcessHanler() {
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(RBACLogoutProcessHandler.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_LOGOUT_PROCESS_HANDLER_BEAN_ID));
	}
	
	private void createDefaultPrivilegeProcessHandler() {
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(RBACPrivilegeProcessHandler.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_PRIVILEGE_PROCESS_HANDLER_BEAN_ID));		
	}
	
	private void createDefaultLoginProcessHandler() {
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(RBACLoginProcessHandler.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_LOGIN_PROCESS_HANDLER_BEAN_ID));
	}
	
	private void createDefaultLoginResultHandler() {
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(RBACLoginResultHandler.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_LOGIN_RESULT_HANDLER_BEAN_ID));
	}
	
	// create managers
	private void createAuthenticationManager() {
		Element loginEle = DomUtils.getChildElementByTagName(rootElement, "login-filter");
		Element authenticationManagerEle = DomUtils.getChildElementByTagName(loginEle, "authentication-manager");
		String userSourceServiceName;
		String passwordEncryptorName;
		if (DomUtils.getChildElementByTagName(authenticationManagerEle, "user-source-service") != null) {
			userSourceServiceName = DomUtils.getChildElementByTagName(authenticationManagerEle, "user-source-service").getTextContent();
		} else {
			userSourceServiceName = "";
		}
		if (DomUtils.getChildElementByTagName(authenticationManagerEle, "password-encryptor") != null) {
			passwordEncryptorName = DomUtils.getChildElementByTagName(authenticationManagerEle, "password-encryptor").getTextContent();
		} else {
			passwordEncryptorName = "";
		}
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(AuthenticationManager.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyReference("userSourceService", userSourceServiceName);
		beanBuilder.addPropertyReference("passwordEncryptor", passwordEncryptorName);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_AUTHENTICATION_MANAGER_BEAN_ID));
	}
	
	private void createPrivilegeManager() {
		Element privilegeEle = DomUtils.getChildElementByTagName(rootElement, "privilege-filter");
		Element privilegeManagerEle = DomUtils.getChildElementByTagName(privilegeEle, "privilege-manager");
		String accessDeniedPage;
		String roleSourceServiceName;
		String permissionSourceServiceName;
		accessDeniedPage = privilegeManagerEle.getAttribute("access-denied-page");
		if (DomUtils.getChildElementByTagName(privilegeManagerEle, "role-source-service") != null) {
			roleSourceServiceName = DomUtils.getChildElementByTagName(privilegeManagerEle, "role-source-service").getTextContent();
		} else {
			// TODO for default role source service
			roleSourceServiceName = "";
		}
		if (DomUtils.getChildElementByTagName(privilegeManagerEle, "permission-source-service") != null) {
			permissionSourceServiceName = DomUtils.getChildElementByTagName(privilegeManagerEle, "permission-source-service").getTextContent();
		} else {
			// TODO for default permission source service
			permissionSourceServiceName = "";
		}
		final Object source = parserContext.extractSource(rootElement);
		BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(PrivilegeManager.class);
		beanBuilder.getRawBeanDefinition().setSource(source);
		beanBuilder.addPropertyValue("accessDeniedPage", accessDeniedPage);
		beanBuilder.addPropertyReference("roleSourceService", roleSourceServiceName);
		beanBuilder.addPropertyReference("permissionSourceService", permissionSourceServiceName);
		BeanDefinition bean = beanBuilder.getBeanDefinition();
		parserContext.registerBeanComponent(new BeanComponentDefinition(bean, DEFAULT_PRIVILEGE_MANAGER_BEAN_ID));
	}
}
